/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef EVENT_MNG_HH
#define EVENT_MNG_HH
//! \addtogroup introsight_framework
//@{

// INCLUDES
#include "util.hh"
#include "refcnt.hh"
#include "introsight.hh"

// DEFINITIONS
#define EM_BUTKEY_OFF         0x0f0 // used to map mouse buttons into keycodes
#define EM_KEYMAP_SIZE        0x100

// ACTIONS
#define EM_ACTION_TYPE_SIZE   8
#define EM_ACTION_SEQ_SIZE    4
#define EM_ACTION_COORD_SIZE  4
#define EM_ACTION_CURSOR_SIZE 4
#define EM_ACTION_KEY_SIZE    1
#define EM_ACTION_EVENT_SIZE  4
#define EM_ACTION_MASK(x)     ((1<<(x))-1)
#define EM_ACTION_NONE        0

enum action_type
{
    EM_ACTION_TYPE_NONE=0,
    // internal types: these ones are not transfered to user (from input devices)
    EM_ACTION_TYPE_INT_V0P,    // vector 0 positive
    EM_ACTION_TYPE_INT_V0N,    // vector 0 negative
    EM_ACTION_TYPE_INT_V1P,    // vector 1 positive
    EM_ACTION_TYPE_INT_V1N,    // vector 1 negative
    EM_ACTION_TYPE_INT_V2P,    // vector 2 positive
    EM_ACTION_TYPE_INT_V2N,    // vector 2 negative
    EM_ACTION_TYPE_INT_V3P,    // vector 3 positive
    EM_ACTION_TYPE_INT_V3N,    // vector 3 negative
    EM_ACTION_TYPE_INT_VLAST,
    EM_ACTION_TYPE_INT_LAST=EM_ACTION_TYPE_INT_VLAST,
    // user types (from input devices)
    EM_ACTION_TYPE_CENTER,     // center selector action
    EM_ACTION_TYPE_INDEX_0,
    EM_ACTION_TYPE_INDEX_1,
    EM_ACTION_TYPE_INDEX_2,
    EM_ACTION_TYPE_INDEX_3,
    // from other objects, may have parameters stored in render_mng
    // this actions are not registered
    EM_ACTION_TYPE_OBJ_CAMERA_ATTRACTOR,
};

enum action_sequence_type
{
    EM_ACTION_SEQ_ONCE=0,      // action sent on key/button release
    EM_ACTION_SEQ_ONCE_PRESS,  // action focus set during key/button press
    EM_ACTION_SEQ_TWICE,       // action focus starts on first time release and ends on second
    EM_ACTION_SEQ_CANCEL,      // used to cancel actions of type ONCE_PRESS and TWICE
};

enum action_coord_type
{
    EM_ACTION_COORD_NONE=0,    // no special coordinates (EM_ACTION_EVENT_MOVE not sent)
    EM_ACTION_COORD_NORMAL,    // window coordinates
    EM_ACTION_COORD_DIFF,      // diferential coordinates
    EM_ACTION_COORD_DIFF_INF,  // diferential coordinates, no window limit
    EM_ACTION_COORD_INC_INF,   // incremental coordinates, no window limit
};

enum action_cursor_type
{
    EM_ACTION_CURSOR_NORMAL=0, // default cursor
    EM_ACTION_CURSOR_INVISIBLE,// invisible cursor
    EM_ACTION_CURSOR_CENTER,   // center cross (for aiming)
};

enum action_keyboard_type
{
    EM_ACTION_KEY_NORMAL,      // normal keyboard status
    EM_ACTION_KEY_NO_REP,      // no key repetitions
};

enum action_event_type
{
    EM_ACTION_EVENT_INIT=0,
    EM_ACTION_EVENT_FINISH,
    EM_ACTION_EVENT_CANCEL,
    EM_ACTION_EVENT_MOVE,
    EM_ACTION_EVENT_VECTOR,
};

// util definitions
#define EM_ACTION_N_VECTOR     ((EM_ACTION_TYPE_INT_VLAST-EM_ACTION_TYPE_INT_V0P)>>1)

// macro to create an action
#define EM_SET_ACTION(type, sequence, coord, cursor, key)		\
(									\
    ((type)&								\
     EM_ACTION_MASK(EM_ACTION_TYPE_SIZE)) |				\
    									\
    (((sequence)&EM_ACTION_MASK(EM_ACTION_SEQ_SIZE))<<			\
     EM_ACTION_TYPE_SIZE) |						\
    									\
    (((coord)&EM_ACTION_MASK(EM_ACTION_COORD_SIZE))<<			\
     (EM_ACTION_TYPE_SIZE+						\
      EM_ACTION_SEQ_SIZE)) |						\
    									\
    (((cursor)&EM_ACTION_MASK(EM_ACTION_CURSOR_SIZE))<<			\
     (EM_ACTION_TYPE_SIZE+						\
      EM_ACTION_SEQ_SIZE+						\
      EM_ACTION_COORD_SIZE)) |						\
    									\
    (((key)&EM_ACTION_MASK(EM_ACTION_KEY_SIZE))<<			\
     (EM_ACTION_TYPE_SIZE+						\
      EM_ACTION_SEQ_SIZE+						\
      EM_ACTION_COORD_SIZE+						\
      EM_ACTION_CURSOR_SIZE))						\
)

#define EM_SET_ACTION_EVENT(action, event)	\
    (((action)&((1<<(EM_ACTION_TYPE_SIZE+	\
		     EM_ACTION_SEQ_SIZE+	\
		     EM_ACTION_COORD_SIZE+	\
		     EM_ACTION_CURSOR_SIZE+	\
		     EM_ACTION_KEY_SIZE))-1)	\
	) |					\
      (event)<<(EM_ACTION_TYPE_SIZE+		\
		EM_ACTION_SEQ_SIZE+		\
		EM_ACTION_COORD_SIZE+		\
		EM_ACTION_CURSOR_SIZE+		\
		EM_ACTION_KEY_SIZE))

// macros to get action specification
#define EM_GET_ACTION_TYPE(action)		\
    ((action)&					\
     EM_ACTION_MASK(EM_ACTION_TYPE_SIZE))

#define EM_GET_ACTION_SEQUENCE(action)		\
    (((action)>>EM_ACTION_TYPE_SIZE)&		\
     EM_ACTION_MASK(EM_ACTION_SEQ_SIZE))

#define EM_GET_ACTION_COORD(action)		\
    (((action)>>(EM_ACTION_TYPE_SIZE+		\
		 EM_ACTION_SEQ_SIZE))&		\
     EM_ACTION_MASK(EM_ACTION_COORD_SIZE))

#define EM_GET_ACTION_CURSOR(action)		\
    (((action)>>(EM_ACTION_TYPE_SIZE+		\
		 EM_ACTION_SEQ_SIZE+		\
		 EM_ACTION_COORD_SIZE))&	\
     EM_ACTION_MASK(EM_ACTION_CURSOR_SIZE))

#define EM_GET_ACTION_KEY(action)		\
    (((action)>>(EM_ACTION_TYPE_SIZE+		\
		 EM_ACTION_SEQ_SIZE+		\
		 EM_ACTION_COORD_SIZE+		\
		 EM_ACTION_CURSOR_SIZE))&	\
     EM_ACTION_MASK(EM_ACTION_KEY_SIZE))

#define EM_GET_ACTION_EVENT(action)		\
    (((action)>>(EM_ACTION_TYPE_SIZE+		\
		 EM_ACTION_SEQ_SIZE+		\
		 EM_ACTION_COORD_SIZE+		\
		 EM_ACTION_CURSOR_SIZE+		\
		 EM_ACTION_KEY_SIZE))&		\
     EM_ACTION_MASK(EM_ACTION_EVENT_SIZE))



// DECLARATIONS
class ogl_window;
class render_obj;

// TYPEDEFS
// callback proto
typedef void (*event_callback_type)(reference_counter *obj, void *data);

class event_mng: public reference_counter
{
    // window to take events from
    ogl_window *w;

    // status of configure and expose
    bool need_complete_redraw;
    bool need_update_from_back;
    int update_coor[IS_WXYWH]; // in OpenGL coordinates

    // status general
    unsigned event_last;    // last OGL_XXX_EVENT (KEY and BUTTON unified in KEY event)
    int event_last_time;    // in milisec

    // status of mouse motion
    int cursor[IS_2D];      // window OGL coords; used in free cursor mode
    bool cursor_moved;      // set to true when coordinates are changed
    bool cursor_in;         // true if cursor is in the window (X window)

    // status of keyboard and mouse buttons (unified in KEY event)
    int key_last_code;      // [0, EM_KEYMAP_SIZE)
    int key_last_status;    // X.h key and button masks
    bool key_last_press;    // press/release
    bool keymap_status[EM_KEYMAP_SIZE];
    int keymap_last_time[EM_KEYMAP_SIZE];
    int keymap_accu_time[EM_KEYMAP_SIZE];

    // callback
    event_callback_type callback;
    reference_counter *callback_obj;
    void *callback_data;
    unsigned callback_events; // OGL_XXX_EVENT bits (KEY and BUTTON distinguished)

    // registered actions
    struct action_str
    {
	unsigned obj_id;            // IS_TYPE_*
	unsigned obj_status;        // IS_STATUS_*  (focus, may have action associated)
	unsigned selector_id;       // IS_SEL_*
	unsigned short key_code;
	unsigned short key_status;
	unsigned action;
	const char *tip;
    };
    action_str **action;            // TODO: sort this list!
    int n_action, max_action;

    unsigned action_last;           // last action, use EM_GET_ACTION_* to get specification
    unsigned action_last_selector;  // last action selector

    // internal actions: vectors
    int action_vector_pos[EM_ACTION_N_VECTOR];
    float action_vector_posf[EM_ACTION_N_VECTOR];
    bool action_vector_press[EM_ACTION_N_VECTOR][2];
    int action_vector_time;

    // PRIVATE METHODS
    void ctor();
    void dtor();

    // event dispatching from inside this class
    void event_configure(int x, int y, int width, int height, bool resize);
    void event_expose(int x, int y, int width, int height);
    void event_motion(int x, int y, int time);
    void event_button(int x, int y, int time, int button, int state, bool press);
    void event_key(int x, int y, int time, int keycode, int state, bool press);
    void event_enter_leave(bool enter);
    void event_update_keymap();

    // registered actions
    void action_register_selector();
    // search for registration
    int action_search(unsigned obj_id, unsigned obj_status, unsigned selector_id, unsigned action_spec);
    // search for action events
    int action_search(unsigned obj_id, unsigned obj_status, unsigned selector_id, unsigned short key_code, unsigned short key_status);
    // sort actions
    static int action_sort_compare(const void *a, const void *b);
    void action_sort();

public:
    event_mng();
    virtual ~event_mng();

    bool init(ogl_window *w);

    //! @name Window redraw and updates
    //@{
    // return the window coordinates and unsets need_update or NULL if no update required
    int *update_required();
    // return true if complete render required (in this case, flag is unset)
    bool redraw_required(); //@}

    //! @name Event dispatching
    // returns the number of usecs until next refresh
    int event_dispatcher();

    //! @name Callback
    //@{
    void set_callback(event_callback_type callback, reference_counter *obj=NULL, void *data=NULL);
    void set_callback_events(unsigned events); // OGL_XXX_EVENT (KEY and BUTTON distinguished)
    //@}

    //! @name Mouse pointer (cursor)
    //@{
    inline int get_cursor(int i_coord) {return cursor[i_coord];}
    inline void set_cursor(int i_coord, int value) {cursor[i_coord]=value;}
    inline bool get_cursor_moved() {return cursor_moved;}
    inline void set_cursor_moved() {cursor_moved=true;}
    inline void reset_cursor_moved() {cursor_moved=false;}
    inline bool get_cursor_in() {return cursor_in;}
    // set cursor in the middle of the window and returns difference (returns true if movement)
    bool cursor_block(int coord[IS_2D]);
    void cursor_set_position(int coord[IS_2D]); //@}

    //! @name Event querying from outside class
    //@{
    inline unsigned get_event_last() {return event_last;}  // returns OGL_XXX_EVENT (KEY and BUTTON unified in KEY event)
    inline int get_event_last_time() {return event_last_time;}
    inline int get_key_last_code() {return key_last_code;}
    inline int get_key_last_status() {return key_last_status;}
    inline bool get_key_last_press() {return key_last_press;}
    inline int get_key_last_accu_time() {return keymap_accu_time[key_last_code];}
    inline void reset_key_last_accu_time() {keymap_accu_time[key_last_code]=0;} //@}

    // to inject events from outside class
    inline void set_event_last(unsigned e) {event_last=e;}

    //! @name Actions
    //@{
    int action_register(unsigned action_spec, // use EM_SET_ACTION
			unsigned short default_keycode,
			unsigned short default_keystatus,
			unsigned obj_id=IS_TYPE_ANY,
			unsigned obj_status=IS_STATUS_ANY, 
			unsigned selector_id=IS_SEL_NONE,
			const char *tip=NULL);

    // returns action specification, use EM_GET_ACTION_*
    unsigned action_search(render_obj *obj, unsigned selector_id=IS_SEL_NONE);

    // dump actions to stdout
    void action_dump();

    // remove all actions
    void action_unregister_all();

    // get all possible actions for object and selection
    // returns the number of possibilities <= max_entries (indexes)
    int action_possibilities(int max_entries, int *entries, unsigned obj_id, unsigned selector_id, unsigned action);

    // get action information from action index
    void action_info(int index, unsigned short *key_code, unsigned short *key_status, const char **tip);

    // last action
    inline unsigned get_action_last() {return action_last;}  // use EM_GET_ACTION_*
    inline void set_action_last(unsigned a) {action_last=a;} // use EM_SET_ACTION
    inline unsigned get_action_last_selector() {return action_last_selector;}
    inline void set_action_last_selector(unsigned s) {action_last_selector=s;}

    // dispatch actions (returns action with event set)
    unsigned action_internal_dispatch(unsigned action);

    // vector
    void action_vector_update();
    void action_vector_updatef(float incs[EM_ACTION_N_VECTOR], float att);
    void action_vector_reset(int i_vector=-1);
    void action_vector_resetf(int i_vector=-1);
    void action_vector_keyboard_reset(int i_vector=-1);
    inline int action_vector_get(int i_vector) {return action_vector_pos[i_vector];}
    inline float action_vector_getf(int i_vector) {return action_vector_posf[i_vector];}
    bool action_vector_active(); // returns true if any vector is changing
    bool action_vector_reset_if_opposite(int i_vector); // if both opposite keys are being pressed, return true and reset
    //@}

    // key translations
    bool key_to_string(unsigned short key_code, unsigned short key_status, char *buffer, int n_buffer);

    // time util
    static int time_now();
};

//@}
#endif

/* Offline metadata attributes:

   @metadata_attrs(
	event_mng
	{
		# members
		w,
		action, n_action, max_action:
						select, read_only
		callback_obj:
						select, read_only, nopickle, private

		# methods
		init,
		action_dump,
		action_unregister_all:
						select
	}
   )
*/
